/*****************************************************************************
 *   Copyright 2005 Tim A Wang
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 *
 ****************************************************************************/
package com.jb.identity.domain;

import com.jb.framework.database.Predicate;
import com.jb.web.application.ApplicationFactoryImpl;
import com.jb.web.application.ApplicationImpl;
import com.jb.web.context.WebContext;
import com.jb.web.model.DBDescriptor;
import com.jb.framework.ModelBean;
import com.jb.framework.PersistentManagerRegistry;
import com.jb.security.SecurityException;
import com.jb.security.SecurityManager;
import com.jb.web.session.SessionUser;

import java.io.Serializable;

import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Locale;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;


public class UserAccount extends ModelBean implements SessionUser, Serializable {
    private static Log log = LogFactory.getLog(UserAccount.class.getName());

    static {
        PersistentManagerRegistry.registerDescriptor(UserAccount.class, new _DBDescriptor());
    }

    private List friends = null;
    private List invitations;
    private List matches = null; 
    private List pendingInvitations;

    private final String DEFAULT_COUNTRY = "840"; //us
    private final String DEFAULT_LANGUAGE = "en";
    private Address billingAddress;

    //address
    private Address homeAddress;
    private Address shippingAddress;

    //Dates
    private Date dateOfBirth;

    //age in decimal
    private Float age;

    //self description
    private String aboutMe;

    //external account number that might be linked to this user
    private String accountNumber;

    //location
    private String country = "840";

    //optional phone number
    private String dayPhoneNumber;
    private String emailAddress;

    //profession
    private String experties;

    //basic info
    private String firstName;

    //gender
    private String gender;

    //communiction
    private String icq;
    private String image;
    private String industry;
    private String interests;

    //localeget
    private String language;
    private String lastName;
    private String logo;
    private String middleName;
    private String mobileNumber;
    private String nightPhoneNumber;
    private String occupation;
    private String password;

    //free text profile
    private String profile;
    private String reference; //external id

    //web friendly info
    private String salutation;
    private String screenName;
    private String securityAnwser;
    private String securityQuestion;
    private String state;
    private String timeZone;

    //security  
    private String userName;
    private String webSite = "";
    private String zipCode;
    private boolean loggedIn;
    private float credit;

    //
    private int access = 100;
    private UserAccount.AccessControl accessControl = new UserAccount.AccessControl(access);

    public UserAccount(String userid) {
        setId(userid);
    }

    public UserAccount() {
        super();
    }

    public static UserAccount getUser(WebContext ctx, String id) throws SecurityException {
        if ((id == null) || (id.length() == 0)) {
            return null;
        }

        UserAccount user = null;

        if ((ApplicationImpl) ApplicationFactoryImpl.getFacesApplication() != null) {
            SecurityManager mgr = ((ApplicationImpl) ApplicationFactoryImpl.getFacesApplication()).getSecurityManager();

            if (mgr != null) {
                user = (UserAccount) mgr.findUser(ctx, id);
            }
        }

        if (user == null) {
            user = (UserAccount) PersistentManagerRegistry.findById(UserAccount.class, id);
        }

        return user;
    }

    public void setAboutMe(String aboutMe) {
        this.aboutMe = aboutMe;
    }

    public String getAboutMe() {
        return aboutMe;
    }

    public void setAccess(int access) {
        if (this.access == access) {
            return;
        }

        this.access = access;
        accessControl = new UserAccount.AccessControl(access);
    }

    public int getAccess() {
        return access;
    }

    public void setAccountNumber(String newAccountNumber) {
        accountNumber = newAccountNumber;
    }

    public String getAccountNumber() {
        return accountNumber;
    }

    //data access potion
    public Address getAddress(WebContext ctx, int addressType) {
        Address address = null;

        if (addressType == com.jb.identity.domain.Address.HOME) {
            address = homeAddress;
        } else if (addressType == com.jb.identity.domain.Address.BILLING) {
            address = shippingAddress;
        } else if (addressType == com.jb.identity.domain.Address.SHIPPING) {
            address = billingAddress;
        }

        if (address != null) {
            return address;
        }

        List list = PersistentManagerRegistry.find(Address.class,
                new Predicate(new Predicate("owner", getId(), "="),
                    new Predicate("addressType", "" + addressType, "="), "and"));

        if (list.size() == 0) {
            address = new Address();
            address.setOwner(getId());
            address.setType(addressType);
        } else {
            address = (Address) list.get(0);
        }

        return address;
    }

    /**
    * Returns age for this user. It is round to one decimal.
    * @return age
    */
    public Float getAge() {
        if (getDateOfBirth() == null) {
            return null;
        }

        if (age != null) {
            return age;
        }

        Calendar birthdate = Calendar.getInstance();
        birthdate.setTime(getDateOfBirth());

        Calendar now = Calendar.getInstance();

        int iAge = now.get(Calendar.YEAR) - birthdate.get(Calendar.YEAR);
        int months = now.get(Calendar.MONTH) - birthdate.get(Calendar.MONTH);

        if (months < 0) {
            iAge--;
            months = 12 + months;
        }

        age = new Float(Math.round(10 * (iAge + Math.abs(months / 12f))) / 10f);

        return age;
    }

    public void setBillingAddress(Address billingAddress) {
        this.billingAddress = billingAddress;
    }

    public Address getBillingAddress() {
        return billingAddress;
    }

    public void setCountry(String country) {
        this.country = country;
    }

    public String getCountry() {
        return (country == null) ? DEFAULT_COUNTRY : country;
    }

    public void setCredit(float creadit) {
        this.credit = creadit;
    }

    public float getCredit() {
        return credit;
    }

    public void setDateOfBirth(Date dateOfBirth) {
        this.dateOfBirth = dateOfBirth;
    }

    public Date getDateOfBirth() {
        return dateOfBirth;
    }

    public void setDayPhoneNumber(String newDayPhoneNumber) {
        dayPhoneNumber = newDayPhoneNumber;
    }

    public String getDayPhoneNumber() {
        return dayPhoneNumber;
    }

    public void setEmailAddress(String newEmailAddress) {
        emailAddress = newEmailAddress;
    }

    public String getEmailAddress() {
        return emailAddress;
    }

    public void setExperties(String experties) {
        this.experties = experties;
    }

    public String getExperties() {
        return experties;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getFirstName() {
        return firstName;
    }

    public Friendship getFriend(String friendName) {
        if (friendName == null) {
            return null;
        }

        return Friendship.getFriendShip(getId(), friendName);
    }

    public boolean isFriend(SessionUser visitor) {
        return (visitor != null) && (getFriend(visitor.getId()) != null);
    }

    public boolean isFriendWtih(WebContext ctx, String userId) {
        return getFriend(userId) != null;
    }

    /**
    * Returns friends in a list.
    * @return friends for this user.
    */
    public List getFriends() {
        if (friends == null) {
            refreshFriends();
        }

        return friends;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    public String getGender() {
        return gender;
    }

    public void setHomeAddress(Address homeAddress) {
        this.homeAddress = homeAddress;
    }

    public Address getHomeAddress() {
        return homeAddress;
    }

    public String getIcon() {
        return image;
    }

    public void setIcq(String icq) {
        this.icq = icq;
    }

    public String getIcq() {
        return icq;
    }

    public void setImage(String image) {
        this.image = image;
    }

    public String getImage() {
        return image;
    }

    public void setIndustry(String industry) {
        this.industry = industry;
    }

    public String getIndustry() {
        return industry;
    }

    public void setInterests(String interests) {
        this.interests = interests;
    }

    public String getInterests() {
        return interests;
    }

    public List getInvitations() {
        if (invitations == null) {
            invitations = Friendship.getInvitations(this.getId());
        }

        return invitations;
    }

    public boolean isInvited() {
        return getInvitations().size() > 0;
    }

    public boolean isInviting() {
        return getPendingInvitations().size() > 0;
    }

    public void setLanguage(String language) {
        this.language = language;
    }

    /**
     * Returns prefered language.
     *
     * @return DOCUMENT ME!
     */
    public String getLanguage() {
        return (language == null) ? DEFAULT_LANGUAGE : language;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getLastName() {
        return lastName;
    }

    public Locale getLocale() {
        Locale locale = new Locale(getLanguage(), getCountry());

        return locale;
    }

    public void setLoggedIn(boolean loggedIn) {
        this.loggedIn = loggedIn;
    }

    public boolean isLoggedIn() {
        return loggedIn;
    }

    public void setLogo(String logo) {
        this.logo = logo;
    }

    public String getLogo() {
        return logo;
    }

    //Returns a list of people that matches your profile

    /**
    * Returns a list of people that might be of interested to this user.
    * @return list of people.
    */
    public List getMatches() {
        if (matches == null) {
            matches = PersistentManagerRegistry.find(UserAccount.class,
                    new Predicate("userName", this.getUserName(), "!="), 20L);
        }

        return matches;
    }

    public void setMiddleName(String middleName) {
        this.middleName = middleName;
    }

    public String getMiddleName() {
        return middleName;
    }

    public void setMobileNumber(String newMobileNumber) {
        mobileNumber = newMobileNumber;
    }

    public String getMobileNumber() {
        return mobileNumber;
    }

    public void setNightPhoneNumber(String newNightPhoneNumber) {
        nightPhoneNumber = newNightPhoneNumber;
    }

    public String getNightPhoneNumber() {
        return nightPhoneNumber;
    }

    public void setOccupation(String occupation) {
        this.occupation = occupation;
    }

    public String getOccupation() {
        return occupation;
    }

    public void setPassword(String newPassword) {
        password = newPassword;
    }

    //this method is not available for 
    public String getPassword() {
        return password;
    }

    public List getPendingInvitations() {
        if (pendingInvitations == null) {
            pendingInvitations = Friendship.getPendingInvitations(this.getId());
        }

        return pendingInvitations;
    }

    public void setProfile(String profile) {
        this.profile = profile;
    }

    public String getProfile() {
        return profile;
    }

    public void setReference(String reference) {
        this.reference = reference;
    }

    public String getReference() {
        return reference;
    }

    public void setSalutation(String pSalutation) {
        this.salutation = pSalutation;
    }

    public String getSalutation() {
        return salutation;
    }

    public void setScreenName(String screenName) {
        this.screenName = screenName;
    }

    public String getScreenName() {
        return (screenName == null) ? userName : screenName;
    }

    public void setSecurityAnwser(String securityAnwser) {
        this.securityAnwser = securityAnwser;
    }

    public String getSecurityAnwser() {
        return securityAnwser;
    }

    public void setSecurityQuestion(String securityQuestion) {
        this.securityQuestion = securityQuestion;
    }

    public String getSecurityQuestion() {
        return securityQuestion;
    }

    public void setShippingAddress(Address shippingAddress) {
        this.shippingAddress = shippingAddress;
    }

    public Address getShippingAddress() {
        return shippingAddress;
    }

    public void setState(String state) {
        this.state = state;
    }

    public String getState() {
        return state;
    }

    public void setTimeZone(String timeZone) {
        this.timeZone = timeZone;
    }

    public String getTimeZone() {
        return timeZone;
    }

    public static UserAccount getUserByUserName(String userName) {
        List list = PersistentManagerRegistry.find(com.jb.identity.domain.UserAccount.class,
                new Predicate("userName", userName, "="));

        if (list.size() == 0) {
            return null;
        } else {
            return (UserAccount) list.get(0);
        }
    }

    public String getDescription() {
        return this.aboutMe;
    }

    public String getTitle() {
        return this.screenName;
    }

    public void setUserName(String newUserName) {
        userName = newUserName;
    }

    /**
     * Gets userName.
     *
     * @return userName.
     */
    public String getUserName() {
        return userName;
    }

    public void setWebSite(String webSite) {
        this.webSite = webSite;
    }

    public String getWebSite() {
        return webSite;
    }

    public void setZipCode(String zipCode) {
        this.zipCode = zipCode;
    }

    public String getZipCode() {
        return zipCode;
    }

    /**
     * Accepts a friend.
     *
     * @param ctx runtime context.
     * @param userId to be a friend.
     */
    public void acceptFriend(WebContext ctx, String userId) {
        Friendship fs = getFriend(userId);

        if (fs != null) {
            fs.confirmFriendShip(ctx, getId(), userId);
            refreshFriends();
        }
    }

    /**
     * Returns access level to friend's resource.
     *
     * @param friend
     *
     * @return
     */
    public int accessLevel(WebContext ctx, SessionUser friend) {
        int accessLevel = PUBLIC;

        if (friend == null) {
            return accessLevel;
        }

        if (equals(friend)) {
            accessLevel = PRIVATE;
        } else if (getFriend(friend.getId()) != null) {
            accessLevel = FRIEND;
        }

        return accessLevel;
    }

    /**
     * Adds a firend.
     *
     * @param ctx calling context.
     * @param friendId id of the friend.
     */
    public Friendship addFriend(WebContext ctx, String friendId) {
        if (friendId == null) {
            return null;
        }

        UserAccount friend = (UserAccount) ctx.getSession().getSecurityManager().findUser(ctx, friendId);

        if (friend != null) {
            return addFriend(ctx, friend);
        } else {
            return null;
        }
    }

    //add a firend

    /**
    * Adds a friend
    * @param ctx calling context.
    * @param friend to be added.
    * @return friendship established.
    */
    public Friendship addFriend(WebContext ctx, UserAccount friend) {
        if ((friend == null) || friend.getId().equals(getId())) {
            return null;
        }

        Friendship friendship = getFriend(friend.getId());

        if (friendship == null) {
            friendship = Friendship.createFriendShip(ctx, this, friend, "Friend");
            refreshFriends();
            friend.refreshFriends();
        }

        return friendship;
    }

    /**
     * returns 0-9 0 being most restrictive
     **/
    public int birthdayAccess() {
        return accessControl.showBirthday;
    }

    /**
     * Deletes a friend
     *
     * @param ctx DOCUMENT ME!
     * @param friendName DOCUMENT ME!
     */
    public void deleteFriend(WebContext ctx, String friendName) {
        if (log.isDebugEnabled()) {
            log.debug("Delete friend " + friendName + " " + userName);
        }

        if (friendName == null) {
            return;
        }

        Friendship friendship = getFriend(friendName);

        if (friendship != null) {
            friendship.delete(ctx);
            refreshFriends();
        }
    }

    //add a friend

    /**
    * Deletes a friend.
    * @param ctx calling context.
    * @param friend to be removed.
    */
    public void deleteFriend(WebContext ctx, UserAccount friend) {
        if (log.isDebugEnabled()) {
            log.debug("Delete friend " + friend + " " + getId());
        }

        if (friend == null) {
            return;
        }

        Friendship friendship = getFriend(friend.getId());

        if (friendship != null) {
            friendship.delete(ctx);
            refreshFriends();
        }
    }

    public boolean equals(Object obj) {
        return obj instanceof UserAccount && (userName != null) && userName.equals(((UserAccount) obj).getUserName());
    }

    public void onLogin(WebContext ctx) {
        this.loggedIn = true;

        //tell my friends that I have signed on
        this.update(ctx);
    }

    public void onLogout(WebContext ctx) {
        this.loggedIn = false;

        //tell my friends that I have signed off
        this.update(ctx);
    }

    /**
     * returns 0-9 0 being most restrictive
     **/
    public int personalPageAccess() {
        return accessControl.showPersonalPage;
    }

    private void refreshFriends() {
        friends = Friendship.getFriends(getId());
        pendingInvitations = null;
        invitations = null;
    }


    static class AccessControl {
        int showBirthday = 9; //
        int showPersonalPage = 9;

        AccessControl(float access) {
        }
    }

    //this i
    static class _DBDescriptor extends DBDescriptor {
        _DBDescriptor() {
            addUniqueIndex("userName", new String[] { "userName" });
            addIndex("name", new String[] { "firstName", "lastName" });
        }

        public int getFieldLength(String fieldName) {
            if (fieldName.equals("aboutMe")) {
                return 1024;
            } else if (fieldName.equals("emailAddress")) {
                return 255;
            } else if (fieldName.equals("image")) {
                return 255;
            } else if (fieldName.equals("interests")) {
                return 1024;
            } else if (fieldName.equals("experties")) {
                return 1024;
            } else if (fieldName.equals("webSite")) {
                return 255;
            } else {
                return super.getFieldLength(fieldName);
            }
        }

        public boolean isPersistable(String fieldName) {
            if (fieldName.equals("homeAddress")) {
                return false;
            } else if (fieldName.equals("shippingAddress")) {
                return false;
            } else if (fieldName.equals("billingAddress")) {
                return false;
            } else {
                return super.isPersistable(fieldName);
            }
        }
    }
}
